﻿#region

using System;
using System.Collections.Generic;
using System.Linq;
using JsMapper.BL.Config;
using JsMapper.BO;
using JsMapper.BO.Events;

#endregion

namespace JsMapper.BL
{
    public static class JsModelManager
    {
        static JsModelManager()
        {
            ModelRepository = new JsModelRepository();
            ConfigMngr = new JsModelConfigManager();
            LoadedMappers = new List<IJsModelMapper>();

            ConfigMngr.OnCollection += OnJsModelCollection;
            ConfigMngr.OnUseCache += OnUseCache;
        }

        public static JsModelRepository ModelRepository { get; set; }
        public static JsModelConfigManager ConfigMngr { get; set; }

        private static Dictionary<Type, Dictionary<Type, string>> JsModelsCache { get; set; }
        private static Dictionary<Type, string> AllJsModelsAsOneCache { get; set; }
        private static List<IJsModelMapper> LoadedMappers { get; set; }

        private static void OnJsModelCollection(object sender, JsModelCollectionEventArgs args)
        {
            ModelRepository.AddRange(args.JsModelCollection);

            if (JsModelsCache != null) JsModelsCache.Clear();
            if (AllJsModelsAsOneCache != null) AllJsModelsAsOneCache.Clear();
        }

        private static void OnUseCache(object sender, JsModelCacheEventArgs args)
        {
            JsModelsCache = new Dictionary<Type, Dictionary<Type, string>>();
            AllJsModelsAsOneCache = new Dictionary<Type, string>();
        }

        private static T GetMapper<T>() where T : class, IJsModelMapper
        {
            var mapper = LoadedMappers.OfType<T>().FirstOrDefault();
            if (mapper != null) return mapper;

            var mapperInst = Activator.CreateInstance<T>();
            LoadedMappers.Add(mapperInst);

            return mapperInst;
        }

        public static string GetJsOutput(IJsModelMapper mapper, Type modelType)
        {
            var meth = typeof(JsModelManager).GetMethod("GetJsOutput", new [] {typeof(Type)});
            var genMeth = meth.MakeGenericMethod(new[] { mapper.GetType() });

            var jsOutput = genMeth.Invoke(null, new object[] {modelType});
            return (string) jsOutput;
        }

        public static string GetJsOutput<T>(Type modelType) where T : class, IJsModelMapper
        {
            if (ConfigMngr.Config.UseCache && JsModelsCache.ContainsKey(typeof (T)) &&
                JsModelsCache[typeof (T)].ContainsKey(modelType)) return JsModelsCache[typeof (T)][modelType];

            var mapper = GetMapper<T>();
            var jsModel = ModelRepository.Get(modelType);
            if (jsModel == null) jsModel = JsModelBuilder.Create<T>();

            var output = JsOutputBuilder.ForJsModel(jsModel, ConfigMngr.Config, mapper);

            if (ConfigMngr.Config.UseCache)
            {
                if (!JsModelsCache.ContainsKey(typeof (T)))
                    JsModelsCache.Add(typeof (T), new Dictionary<Type, string>());

                JsModelsCache[typeof (T)].Add(modelType, output);
            }

            return output;
        }

        public static string GetAllJsOutputAsOne(IJsModelMapper mapper)
        {
            var meth = typeof(JsModelManager).GetMethod("GetAllJsOutputAsOne", Type.EmptyTypes);
            var genMeth = meth.MakeGenericMethod(new[] { mapper.GetType() });

            var jsOutput = genMeth.Invoke(null, null);
            return (string) jsOutput;
        }

        public static string GetAllJsOutputAsOne<T>() where T : class, IJsModelMapper
        {
            if (ConfigMngr.Config.UseCache &&
                AllJsModelsAsOneCache.ContainsKey(typeof (T)) &&
                !string.IsNullOrEmpty(AllJsModelsAsOneCache[typeof (T)])) return AllJsModelsAsOneCache[typeof (T)];

            var models = GetAllJsOutput<T>().Aggregate(string.Empty, (current, t) => current + t);

            if (ConfigMngr.Config.UseCache && !AllJsModelsAsOneCache.ContainsKey(typeof (T)))
                AllJsModelsAsOneCache.Add(typeof (T), models);
            return models;
        }

        public static IEnumerable<string> GetAllJsOutput<T>() where T : class, IJsModelMapper
        {
            return ModelRepository.GetAll().Select(model => GetJsOutput<T>(model.ModelType));
        }

        public static IEnumerable<string> GetAllJsOutput(IJsModelMapper mapper)
        {
            var meth = typeof(JsModelManager).GetMethod("GetAllJsOutput", Type.EmptyTypes);
            var genMeth = meth.MakeGenericMethod(new[] { mapper.GetType() });

            var jsOutput = genMeth.Invoke(null, null);
            return (IEnumerable<string>) jsOutput;
        }
    }
}